/*
 * @(#)VersionID.java 1.7 05/11/17 Copyright (c) 2006 Sun Microsystems, Inc. All
 * Rights Reserved. Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following conditions
 * are met: -Redistribution of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer. -Redistribution
 * in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution. Neither the name of Sun
 * Microsystems, Inc. or the names of contributors may be used to endorse or
 * promote products derived from this software without specific prior written
 * permission. This software is provided "AS IS," without a warranty of any
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC.
 * ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY
 * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. You
 * acknowledge that this software is not designed, licensed or intended for use
 * in the design, construction, operation or maintenance of any nuclear
 * facility.
 */

package jnlp.sample.util;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * VersionID contains a JNLP version ID. The VersionID also contains a prefix
 * indicator that can be used when stored with a VersionString
 */
public class VersionID implements Comparable
{
    private String[] _tuple; // Array of Integer or String objects

    private boolean _usePrefixMatch; // star (*) prefix

    private boolean _useGreaterThan; // plus (+) greather-than

    private boolean _isCompound; // and (&) operator

    private VersionID _rest; // remaining part after the &

    /** Creates a VersionID object */
    public VersionID(String str)
    {
	_usePrefixMatch = false;
	_useGreaterThan = false;
	_isCompound = false;
	if (str == null && str.length() == 0)
	{
	    _tuple = new String[0];
	    return;
	}

	// Check for compound
	int amp = str.indexOf("&");
	if (amp >= 0)
	{
	    _isCompound = true;
	    VersionID firstPart = new VersionID(str.substring(0, amp));
	    _rest = new VersionID(str.substring(amp + 1));
	    _tuple = firstPart._tuple;
	    _usePrefixMatch = firstPart._usePrefixMatch;
	    _useGreaterThan = firstPart._useGreaterThan;
	}
	else
	{
	    // Check for postfix
	    if (str.endsWith("+"))
	    {
		_useGreaterThan = true;
		str = str.substring(0, str.length() - 1);
	    }
	    else if (str.endsWith("*"))
	    {
		_usePrefixMatch = true;
		str = str.substring(0, str.length() - 1);
	    }

	    ArrayList list = new ArrayList();
	    int start = 0;
	    for (int i = 0; i < str.length(); i++)
	    {
		// Split at each separator character
		if (".-_".indexOf(str.charAt(i)) != -1)
		{
		    if (start < i)
		    {
			String value = str.substring(start, i);
			list.add(value);
		    }
		    start = i + 1;
		}
	    }
	    if (start < str.length())
	    {
		list.add(str.substring(start, str.length()));
	    }
	    _tuple = new String[list.size()];
	    _tuple = (String[]) list.toArray(_tuple);
	}
    }

    /** Returns true if no flags are set */
    public boolean isSimpleVersion()
    {
	return !_useGreaterThan && !_usePrefixMatch && !_isCompound;
    }

    /**
     * Match 'this' versionID against vid. The _usePrefixMatch/_useGreaterThan
     * flag is used to determine if a prefix match of an exact match should be
     * performed if _isCompound, must match _rest also.
     */
    public boolean match(VersionID vid)
    {
	if (_isCompound)
	{
	    if (!_rest.match(vid))
	    {
		return false;
	    }
	}
	return (_usePrefixMatch) ? this.isPrefixMatch(vid) : (_useGreaterThan) ? vid
		.isGreaterThanOrEqual(this) : matchTuple(vid);
    }

    /** Compares if two version IDs are equal */
    public boolean equals(Object o)
    {
	if (matchTuple(o))
	{
	    VersionID ov = (VersionID) o;
	    if (_rest == null || _rest.equals(ov._rest))
	    {
		if ((_useGreaterThan == ov._useGreaterThan)
			&& (_usePrefixMatch == ov._usePrefixMatch))
		{
		    return true;
		}
	    }
	}
	return false;
    }

    /** Compares if two version IDs are equal */
    private boolean matchTuple(Object o)
    {
	// Check for null and type
	if (o == null || !(o instanceof VersionID))
	    return false;
	VersionID vid = (VersionID) o;

	// Normalize arrays
	String[] t1 = normalize(_tuple, vid._tuple.length);
	String[] t2 = normalize(vid._tuple, _tuple.length);

	// Check contents
	for (int i = 0; i < t1.length; i++)
	{
	    Object o1 = getValueAsObject(t1[i]);
	    Object o2 = getValueAsObject(t2[i]);
	    if (!o1.equals(o2))
		return false;
	}
	return true;
    }

    private Object getValueAsObject(String value)
    {
	if (value.length() > 0 && value.charAt(0) != '-')
	{
	    try
	    {
		return Integer.valueOf(value);
	    }
	    catch (NumberFormatException nfe)
	    { /* fall through */
	    }
	}
	return value;
    }

    public boolean isGreaterThan(VersionID vid)
    {
	return isGreaterThanOrEqualHelper(vid, false);
    }

    public boolean isGreaterThanOrEqual(VersionID vid)
    {
	return isGreaterThanOrEqualHelper(vid, true);
    }

    /** Compares if 'this' is greater than vid */
    private boolean isGreaterThanOrEqualHelper(VersionID vid, boolean allowEqual)
    {

	if (_isCompound)
	{
	    if (!_rest.isGreaterThanOrEqualHelper(vid, allowEqual))
	    {
		return false;
	    }
	}
	// Normalize the two strings
	String[] t1 = normalize(_tuple, vid._tuple.length);
	String[] t2 = normalize(vid._tuple, _tuple.length);

	for (int i = 0; i < t1.length; i++)
	{
	    // Compare current element
	    Object e1 = getValueAsObject(t1[i]);
	    Object e2 = getValueAsObject(t2[i]);
	    if (e1.equals(e2))
	    {
		// So far so good
	    }
	    else
	    {
		if (e1 instanceof Integer && e2 instanceof Integer)
		{
		    return ((Integer) e1).intValue() > ((Integer) e2).intValue();
		}
		else
		{
		    String s1 = t1[i].toString();
		    String s2 = t2[i].toString();
		    return s1.compareTo(s2) > 0;
		}

	    }
	}
	// If we get here, they are equal
	return allowEqual;
    }

    /** Checks if 'this' is a prefix of vid */
    public boolean isPrefixMatch(VersionID vid)
    {

	if (_isCompound)
	{
	    if (!_rest.isPrefixMatch(vid))
	    {
		return false;
	    }
	}
	// Make sure that vid is at least as long as the prefix
	String[] t2 = normalize(vid._tuple, _tuple.length);

	for (int i = 0; i < _tuple.length; i++)
	{
	    Object e1 = _tuple[i];
	    Object e2 = t2[i];
	    if (e1.equals(e2))
	    {
		// So far so good
	    }
	    else
	    {
		// Not a prefix
		return false;
	    }
	}
	return true;
    }

    /** Normalize an array to a certain lengh */
    private String[] normalize(String[] list, int minlength)
    {
	if (list.length < minlength)
	{
	    // Need to do padding
	    String[] newlist = new String[minlength];
	    System.arraycopy(list, 0, newlist, 0, list.length);
	    Arrays.fill(newlist, list.length, newlist.length, "0");
	    return newlist;
	}
	else
	{
	    return list;
	}
    }

    public int compareTo(Object o)
    {
	if (o == null || !(o instanceof VersionID))
	    return -1;
	VersionID vid = (VersionID) o;
	return equals(vid) ? 0 : (isGreaterThanOrEqual(vid) ? 1 : -1);
    }

    /** Show it as a string */
    public String toString()
    {
	StringBuffer sb = new StringBuffer();
	for (int i = 0; i < _tuple.length - 1; i++)
	{
	    sb.append(_tuple[i]);
	    sb.append('.');
	}
	if (_tuple.length > 0)
	    sb.append(_tuple[_tuple.length - 1]);
	if (_usePrefixMatch)
	    sb.append('+');
	return sb.toString();
    }
}
